#pragma once
#include "utils.hpp"
#include "config.hpp"
#include <unordered_map>
#include <pthread.h>
#include <mutex>

namespace Cloud
{
    struct Writer
    {
        pthread_rwlock_t *_lock;
        Writer(pthread_rwlock_t *lock) : _lock(lock) { pthread_rwlock_wrlock(_lock); }
        ~Writer() { pthread_rwlock_unlock(_lock); }
    };

    struct Reader
    {
        pthread_rwlock_t *_lock;
        Reader(pthread_rwlock_t *lock) : _lock(lock) { pthread_rwlock_rdlock(_lock); }
        ~Reader() { pthread_rwlock_unlock(_lock); }
    };

    struct FileInfo
    {
        FileInfo() {}
        FileInfo(const std::string &backPath) { init(backPath); }
        void init(const std::string &backPath)
        {
            FileUtils f(backPath);
            if (!f.exists())
            {
                std::cerr << "FileInfo::init: " << backPath << " not exist\n";
                return;
            }

            _isPacked = false;
            _fsize = f.getSize();
            _mtime = f.getMTime();
            _atime = f.getATime();
            _backPath = backPath;

            Config *pc = Config::getInstance();
            _packPath = pc->getPackDir() + f.getFileName() + pc->getPackfileSuffix();
            _url = pc->getDownloadPrefix() + f.getFileName();
        }
        bool _isPacked;
        size_t _fsize;
        time_t _mtime;
        time_t _atime;
        std::string _backPath;
        std::string _packPath;
        std::string _url;
    };

    class DataManager
    {
    private:
        DataManager()
            : _backupFile(Config::getInstance()->getBackupFile())
        {
            std::cout << "create DataManager\n";
            if (pthread_rwlock_init(&_rwlock, nullptr))
                std::cerr << "DataManager: init _rwlock error\n";

            std::cout << "DataManager begin loadinfo\n";
            loadInfo();
            std::cout << "DataManager is created!\n";
        }
        DataManager(const DataManager &) = delete;
        DataManager &operator=(const DataManager &) = delete;
        void fileInfoToJson(const FileInfo &fileInfo, Json::Value &cur)
        {
            cur["isPacked"] = fileInfo._isPacked;
            cur["atime"] = static_cast<Json::Int64>(fileInfo._atime);
            cur["mtime"] = static_cast<Json::Int64>(fileInfo._mtime);
            cur["fsize"] = static_cast<Json::Int64>(fileInfo._fsize);
            cur["packPath"] = fileInfo._packPath;
            cur["backPath"] = fileInfo._backPath;
            cur["url"] = fileInfo._url;
        }
        void jsonToFileInfo(const Json::Value &root, FileInfo &cur)
        {
            cur._isPacked = root["isPacked"].asBool();
            cur._atime = root["atime"].asInt64();
            cur._mtime = root["mtime"].asInt64();
            cur._fsize = root["fsize"].asInt64();
            cur._packPath = root["packPath"].asString();
            cur._backPath = root["backPath"].asString();
            cur._url = root["url"].asString();
        }

    public:
        static DataManager *getInstance()
        {
            if (_instance == nullptr)
            {
                _mutex.lock();
                if (_instance == nullptr)
                    _instance = new DataManager();
                _mutex.unlock();
            }
            return _instance;
        }

        ~DataManager()
        {
            pthread_rwlock_destroy(&_rwlock);
        }

        bool loadInfo()
        {
            if (!_backupFile.getSize())
                return _backupFile.setContent("");
            std::string str;
            if (!_backupFile.getContent(&str))
                return false;
            Json::Value root;
            if (!JsonUtils::deSerialize(str, &root))
                return false;
            for (int i = 0; i < root.size(); i++)
            {
                FileInfo cur;
                jsonToFileInfo(root[i], cur);
                _hash[cur._url] = cur;
            }

            return true;
        }

        bool storage()
        {
            Json::Value root;
            for (const auto &[url, fileInfo] : _hash)
            {
                Json::Value cur;
                fileInfoToJson(fileInfo, cur);
                root.append(cur);
            }
            std::string str;
            JsonUtils::serialize(root, &str);
            return _backupFile.setContent(str);
        }

        bool update(const FileInfo &info)
        {
            Writer w(&_rwlock);
            _hash[info._url] = info;
            return storage();
        }

        bool insert(const FileInfo &info)
        {
            Writer w(&_rwlock);
            _hash[info._url] = info;
            return storage();
        }

        bool getInfoByURL(const std::string &key, FileInfo *pInfo)
        {
            Reader r(&_rwlock);
            if (!pInfo)
                return false;
            if (!_hash.count(key))
                return false;
            *pInfo = _hash[key];
            return true;
        }

        bool getInfoBybackPath(const std::string &path, FileInfo *pInfo)
        {
            Reader r(&_rwlock);
            if (!pInfo)
                return false;
            if (!FileUtils(path).exists())
                return false;
            *pInfo = FileInfo(path);
            return true;
        }
        
        bool getFileInfo(std::vector<FileInfo> *pArr)
        {
            Reader r(&_rwlock);
            if (!pArr)
                return false;
            pArr->clear();
            for (const auto &[url, Info] : _hash)
                pArr->push_back(Info);
            return true;
        }

    private:
        FileUtils _backupFile;
        pthread_rwlock_t _rwlock;
        std::unordered_map<std::string, FileInfo> _hash;

    private:
        static DataManager *_instance;
        static std::mutex _mutex;
    };
    
    std::mutex DataManager::_mutex;
    DataManager *DataManager::_instance = nullptr;
}